﻿#Include ..\..\Extend.ahk
; 模拟富文本

class TextData {

  static defaultStyle := { fg: 'black', bg: 'white', ex: '' }

  __New(ch, fg, bg, ex, key := '') {
    this.fg := fg ; 字体颜色
    this.bg := bg ; 背景颜色
    this.ch := ch ; 字符串
    this.ex := ex ; 扩展属性
    this.events := {}
    this.key := key
  }

  OnEvent(name, cb) {
    switch name {
      case 'click': this.events['click'] := (_, *) => cb(_)
      case 'context': this.events['contextMenu'] := (_, *) => cb(_)
    }
    return this
  }

  static copyFrom(data, ch) {
    copy := TextData(ch ?? data.ch, data.fg, data.bg, data.ex, data.key)
    copy.events := data.events
    return copy
  }

  static of(ch, style := TextData.defaultStyle) {
    return TextData(ch, style.fg, style.bg, style.ex)
  }

}

class RichText {
  static cw := 10

  __New(g, x, y, w, data, options) {
    this.x := x, this.y := y, this._ctrl := [], text := '', this._namedCtrl := Map()
    data := this.Prepare(data, w), _y := y, _x := x, _debug := true & (options['debug'] || 0)
    hbBg := _debug ? '00ff00' : options['bg'], sbBg := _debug ? 'ff0000' : options['bg']
    for i, v in data {
      for vv in v {
        if options['hardBreak'] and vv.ch = '`n' {
          t := g.AddText(Format('Right x{} y{} c{} Background{} w{}', x, y, vv.fg, hbBg || vv.bg, w - x + _x), options['bc'])
          t.GetPos(, , &_w, &_h), this._ctrl.Push({ type: 'hb', ctrl: t })
        } else if vv.ch != '`n' {
          t := g.AddText(Format('x{} y{} c{} Background{}', x, y, vv.fg, vv.bg), T_(vv.ch))
          for name, event in vv.events.OwnProps()
            t.OnEvent(name, event)
          t.GetPos(, , &_w, &_h), t.SetFont(vv.ex), this._ctrl.Push({ type: 'v', ctrl: t })
          if vv.key { ; 保存，不使用 v 选项，因为此项要求唯一
            if !this._namedCtrl.Has(vv.key)
              this._namedCtrl.Set(vv.key, [])
            this._namedCtrl.Get(vv.key).Push(t)
          }
        }
        x += _w, _y := y + _h, text .= vv.ch
        if x - this.x < w and options['softBreak'] {
          t := g.AddText(Format('right x{} y{} w{} Background{}', x, y, w - x + _x, sbBg || vv.bg), '')
          this._ctrl.Push({ type: 'sb', ctrl: t })
        }
      }
      if _debug {
        l := ''
        for vv in v
          l .= vv.ch ? vv.ch : '*'
        g.AddText('x200 y' y, T_(l))
      }
      y := _y, x := _x
    }
    this.h := y - this.y, this.w := w, this.text := text
  }

  static setCW(v) => RichText.cw := v

  GetPos(&x?, &y?, &w?, &h?) {
    IsSet(x) && x := this.x
    IsSet(y) && y := this.y
    IsSet(w) && w := this.w
    IsSet(h) && h := this.h
  }

  Hide() {
    this._ctrl.foreach(v => v.ctrl.Visible := false)
  }

  Show() {
    this._ctrl.foreach(v => v.ctrl.Visible := true)
  }

  Prepare(data, w) {
    r := [], l := 0, ML := w // RichText.cw, i := 1
    tmp := []

    for v in data {
      if v.ch = ''
        continue
      spited := StrSplit(v.ch, '`n'), field := v
      if spited.Length != 1 {
        for vv in spited {
          if vv != ''
            tmp.Push(TextData.copyFrom(field, vv))
          tmp.Push(TextData.copyFrom(field, '`n'))
        }
        tmp.Pop()
      } else tmp.Push(field)
    }

    data := tmp, tmp := []

    while l < ML and i <= data.Length {
      cur := data[i]
      if cur.ch = '`n' {
        tmp.Push(cur), l := ML, i++, r.push(tmp), tmp := [], l := 0
        continue
      }
      _l := _t(cur.ch)
      if ML - l >= _l { ; 可以放下
        tmp.Push(cur), l += _l
        i++
      } else { ; 裁剪
        _i := getIdx(cur.ch, ML - l)
        _s := SubStr(cur.ch, 1, _i)
        tmp.Push(TextData.copyFrom(cur, _s))
        data[i].ch := SubStr(cur.ch, _i + 1)
        l := ML
      }
      if l = ML {
        r.push(tmp), tmp := [], l := 0
      }
    }
    r.Push(tmp)
    return r

    ; 可以使用更精确的方式
    _t(str) => str.toCharArray().reduce((acc, cur) => acc += (IsHan(cur) || IsFullChar(cur)) ? 2 : 1, 0)

    getIdx(str, l) {
      local _l := 0, _i := 1, chs := str.toChs()
      for v in chs {
        if IsFullChar(v) or IsHan(v)
          _l += 2
        else _l += 1
        if _l = l
          return A_Index
        else if _l > l
          return A_Index - 1
      }
      return 0
    }
  }
}